package org.xteam.sled.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.xteam.parser.runtime.Span;
import org.xteam.sled.ast.EqOp;
import org.xteam.sled.ast.Relop;
import org.xteam.sled.semantic.Field;
import org.xteam.sled.semantic.IBinaryFunc;
import org.xteam.sled.semantic.IErrorContext;
import org.xteam.sled.semantic.RangeConstraintsMaker;
import org.xteam.sled.semantic.exp.Exp;
import org.xteam.sled.semantic.exp.ExpConst;
import org.xteam.sled.semantic.exp.ExpRewriter;

public abstract class BasePattern<T extends IDisjunct> {
	
	protected String name;
	protected List<T> disjuncts = new ArrayList<T>();
	
	public BasePattern(String name) {
		this.name = name;
		this.disjuncts = new ArrayList<T>();
	}
	
	public BasePattern(String name, T disjunct) {
		this.name = name;
		this.disjuncts = new ArrayList<T>();
		this.disjuncts.add(disjunct);
	}
	
	public BasePattern(String name, List<T> disjuncts) {
		this.name = name;
		this.disjuncts = disjuncts;
	}
	
	public String name() {
		return name;
	}
	
	public void setName(String name) {
		if (disjuncts.size() == 1 && disjuncts.get(0).name() != null) {
			disjuncts.get(0).setName(name);
			this.name = name;
		} else {
			for (IDisjunct dis : disjuncts) {
				if (dis.name() == null) {
					dis.setName(name);
				}
			}
			if (this.name == null)
				this.name = name;
		}
	}
	
	public BasePattern<T> nameUnnamed(String newName) {
		BasePattern<T> n = createPattern(name == null ? newName : name);
		for (IDisjunct d : disjuncts) {
			n.disjuncts.add((T)d.nameUnnamed(newName));
		}
		return n;
	}
	
	public List<T> getDisjuncts() {
		return disjuncts;
	}
	
	public void add(T disjunct) {
		this.disjuncts.add(disjunct);
	}
	
	public void addConditions(List<Exp> constraints) {
		for (T dis : disjuncts) {
			dis.addConditions(constraints);
		}
	}
	
	public BasePattern<T> or(BasePattern<T> other) {
		BasePattern<T> p = createPattern(null);
		p.disjuncts.addAll(disjuncts);
		p.disjuncts.addAll(other.disjuncts);
		return p;
	}
	
	public BasePattern<T> and(BasePattern<T> other) {
		BasePattern<T> p = distribute(new IBinaryFunc<T>() {
			public T exec(T h, T d) {
				return (T)h.and(d);
			}
		}, other);
		p.dropContradictoryDisjuncts();
		String keepname = isFieldBinding() ? name : other.isFieldBinding() ? other.name : null;
		if (keepname != null && p.name == null)
			p.name = keepname;
		return p;
	}
	
	private void dropContradictoryDisjuncts() {
		Iterator<T> i = disjuncts.iterator();
		while (i.hasNext()) {
			if (i.next().isContradictory())
				i.remove();
		}
	}
	
	private boolean isFieldBinding () {
		if (disjuncts.size() == 1 && ! disjuncts.get(0).hasConditions()) {
			return disjuncts.get(0).isFieldBinding();
		}
		return false;
	}
	
	public BasePattern<T> seq(BasePattern<T> other) {
		return distribute(new IBinaryFunc<T>() {
			public T exec(T h, T d) {
				return (T)h.seq(d);
			}
		}, other);
	}
	
	public BasePattern<T> preDots() {
		BasePattern<T> p = createPattern(name);
		for (T d : disjuncts) {
			p.add((T)d.preDots());
		}
		return p;
	}

	public BasePattern<T> postDots() {
		BasePattern<T> p = createPattern(name);
		for (T d : disjuncts) {
			p.add((T)d.postDots());
		}
		return p;
	}
	
	private BasePattern<T> distribute(IBinaryFunc<T> opdd, BasePattern<T> other) {
		BasePattern<T> result = createPattern(null);
		for (T h : disjuncts) {
			for (T d : other.disjuncts) {
				result.add(opdd.exec(h, d));
			}
		}
		return result;
	}
	
	public BasePattern<T> subst(ExpRewriter subs) {
		BasePattern<T> n = createPattern(name);
		for (IDisjunct d : disjuncts) {
			IDisjunct dis = d.subst(subs);
			boolean hasFalse = false;
			for (Exp e : dis.getConditions()) {
				/*if (e instanceof ExpFalse)
					hasFalse = true;*/
			}
			if (! hasFalse)
				n.disjuncts.add((T)dis);
		}
		return n;
	}
	
	protected abstract BasePattern<T> createPattern(String name);

	@Override
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		if (name != null)
			buffer.append(name).append("=");
		boolean isFirst = true;
		for (T disjuct : disjuncts) {
			if (! isFirst)
				buffer.append("|");
			buffer.append(disjuct.toString());
			isFirst = false;
		}
		return buffer.toString();
	}
	
}
